﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Web;
using up6.db.biz;
using up6.db.model;
using up6.db.utils;
using up6.utils;
using static System.Runtime.CompilerServices.RuntimeHelpers;

namespace api.up6
{
    public partial class f_post : WebBase
    {
        bool safe_check(params string[] ps)
        {
            foreach (var v in ps)
            {
                System.Diagnostics.Debug.Write("参数值：");
                System.Diagnostics.Debug.WriteLine(v);
                if (string.IsNullOrEmpty(v)) return false;
            }
            foreach (string key in Request.Headers.Keys)
            {
                var vs = Request.Headers.GetValues(key);
                //XDebug.Output(key + " "+String.Join(",", vs));
            }
            return true;
        }

        void send_error(string errCode,JObject par=null) {
            new Message(errCode,par);
            this.toContentJson(
                new JObject {
                    { "msg",errCode},
                    { "code",errCode},
                    { "md5","" },//文件块MD5
                    { "offset", -1 },//偏移
                    { "param", par},//参数信息
                });
            Response.End();
        }

        /// <summary>
        /// 验证块大小，并解密块数据
        /// </summary>
        /// <param name="stm">加密块数据</param>
        /// <param name="f">块信息</param>
        /// <returns></returns>
        Stream decryptBlock(Stream stm,ref FileInf f) {
            //未加密
            if (f.blockSizeSec == 0) return stm;

            //块大小不同
            if (stm.Length != f.blockSizeSec)
            {
                this.send_error(UploadErrorCode.blockSizeCryDifferent,
                    new JObject {
                            {"blockSizeRecv",stm.Length },
                            {"blockSizeSend",f.blockSizeSec},
                    });
            }

            //加密存储
            if (f.encrypt) return stm;

            CryptoTool ct = new CryptoTool();
            stm = ct.decode(f.encryptAgo,stm);
            return stm;
        }

        void validBlockSize(Stream s,FileInf f) {
            //已加密，由解密模块判断
            if (f.blockSizeSec > 0) return;

            //使用对象存储
            if (ConfigReader.storage() != StorageType.IO)
            {
                //块大小不能小于5MB
                if (f.blockCount > 1 &&
                    f.blockIndex == 1 &&
                    f.blockSize < 5242880)
                {
                    this.send_error("对象存储块大小小于5MB");
                }
            }

            //未加密
            if(f.blockSize!=s.Length)
            {
                this.send_error(UploadErrorCode.blockSizeCryDifferent,
                    new JObject {
                            {"blockSizeRecv",s.Length },
                            {"blockSizeSend",f.blockSize},
                    });
            }
        }

        /// <summary>
        /// 验证块MD5
        /// </summary>
        /// <param name="blockMd5"></param>
        /// <param name="stm"></param>
        /// <returns></returns>
        bool checkBlockMd5(string blockMd5,Stream stm) {
            if (!string.IsNullOrEmpty(blockMd5))
            {
                string md5Svr = Md5Tool.calc(stm);
                if (md5Svr != blockMd5)
                {
                    this.send_error(UploadErrorCode.blockMd5Different,
                        new JObject {
                            { "md5Svr",md5Svr },
                            { "md5Loc",blockMd5 }
                        });
                    return false;
                }
            }
            return true;
        }

        bool checkToken(string token,FileInf fileSvr) {
            WebSafe ws = new WebSafe();
            if (!ws.validToken(token, fileSvr, "block"))
            {
                this.send_error(UploadErrorCode.blockMd5Different,
                    new JObject {
                        { "action","block" }
                    });
                return false;
            }
            return true;
        }

        Stream unCompress(FileInf f, Stream s)
        {
            //未压缩
            if (f.blockSizeCpr == 0) return s;

            //块大小不同
            if (s.Length != f.blockSizeCpr)
            {
                this.send_error(UploadErrorCode.blockSizeCprDifferent,
                    new JObject {
                            {"blockSizeRecv",s.Length },
                            {"blockSizeSend",f.blockSizeCpr},
                    });
            }

            return UtilsTool.unCompress(s,f.blockCprType);
        }

        bool checkBlockData() {
            if(Request.Files.Count<1)
            {
                this.send_error("blockDataEmpty",
                    new JObject {
                        { "action","block" }
                    });
                return false;
            }
            return true;
        }

        /// <summary>
        /// 只负责拼接文件块。将接收的文件块数据写入到文件中。
        /// 更新记录：
        ///     2022-11-13 优化参数
        ///     2017-07-11 优化参数检查逻辑
        ///     2016-03-31 增加文件夹信息字段
        ///		2015-03-19 文件路径由客户端提供，此页面不再查询文件在服务端的路径。减少一次数据库访问操作。
        ///		2012-04-12 更新文件大小变量类型，增加对2G以上文件的支持。
        ///		2012-10-30 增加更新文件进度功能。
        ///		2012-04-18 取消更新文件上传进度信息逻辑。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            FileInf fileSvr = new FileInf();
            fileSvr.id = this.headStr("id");
            fileSvr.uid = this.headStr("uid");
            fileSvr.pid = this.headStr("pid");
            fileSvr.pidRoot = this.headStr("pidRoot");
            fileSvr.object_id = this.headStr("object_id");
            fileSvr.lenSvr = this.headLong("lenSvr");
            fileSvr.lenLoc = this.headLong("lenLoc");
            fileSvr.lenLocSec = this.headLong("lenLocSec");
            fileSvr.pathLoc = this.formBase64("pathLoc");
            fileSvr.pathSvr = this.formBase64("pathSvr");
            fileSvr.pathRel = this.formBase64("pathRel");
            fileSvr.blockIndex = this.headInt("blockIndex");
            fileSvr.blockOffset = this.headLong("blockOffset");
            fileSvr.blockOffsetCry = this.headLong("blockOffsetCry");
            fileSvr.blockSize = this.headInt("blockSize");//原始块大小
            fileSvr.blockSizeSec = this.headInt("blockSizeCry");//
            fileSvr.blockCount = this.headInt("blockCount");
            fileSvr.nameLoc = PathTool.getName(fileSvr.pathLoc);
            fileSvr.nameSvr = fileSvr.nameLoc;
            fileSvr.encrypt = ConfigReader.storageEncrypt();//加密存储
            fileSvr.encryptAgo = this.headStr("blockEncryptAgo");//加密算法：sm4,aes
            fileSvr.blockSizeCpr = this.headInt("blockSizeCpr");//块压缩大小
            bool blockCpr = this.headBool("blockCpr");//是否压缩了块
            fileSvr.blockCprType = this.headStr("blockCprType");//块压缩方式
            string blockMd5     = this.headStr("blockMd5");//块MD5
            var complete        = this.headBool("complete");//true/false
            string token      = this.headStr("token");//

            if(!this.safe_check(fileSvr.uid,fileSvr.id,fileSvr.pathSvr)) return;
            //有文件块数据
            if (!this.checkBlockData()) return;

            HttpPostedFile file = Request.Files.Get(0);//文件块
            var stm = file.InputStream;

            //计算文件块MD5
            this.checkBlockMd5(blockMd5, stm);

            //解压
            stm = this.unCompress(fileSvr, stm);

            //解密
            stm = this.decryptBlock(stm, ref fileSvr);

            this.validBlockSize(stm, fileSvr);

            //token验证
            this.checkToken(token, fileSvr);

            PathBuilder pb = new PathBuilder();
            fileSvr.pathSvr = pb.relToAbs(fileSvr.pathSvr);

            var fw = ConfigReader.blockWriter();
            bool needGenId = !string.IsNullOrEmpty(fileSvr.pid);
            if (needGenId) needGenId = 1 == fileSvr.blockIndex;
            if (needGenId && 
                (fw.storage == StorageType.FastDFS||
                fw.storage == StorageType.Minio||
                fw.storage == StorageType.OSS ||
                fw.storage == StorageType.OBS)) 
                needGenId = string.IsNullOrEmpty(fileSvr.object_id);
                    
            try
            {
                //第一块->创建或生成对象ID
                if (needGenId)
                {
                    fileSvr.object_id = fw.make(fileSvr);
                    fileSvr.object_key = fileSvr.getObjectKey();
                }

                //子文件=>保存到层级信息文件
                if (!string.IsNullOrEmpty(fileSvr.pidRoot)) fileSvr.saveScheme();

                //写入块数据
                fw.write(fileSvr, stm);

                //合并文件
                if (complete && !fw.writeLastPart(fileSvr))
                {
                    this.send_error("合并文件块错误");
                }

            }
            catch (Exception ie) {
                //this.send_error(UploadErrorCode.writeBlockDataFail,
                //    new JObject {
                //        {"file",JsonConvert.SerializeObject(fileSvr) },
                //        { "exception", ie.Message } });

                this.toContentJson(
                    new JObject {
                    { "msg","error"},
                    { "detail","写入文件块数据错误"+ie.Message},
                    { "md5","" },//文件块MD5
                    { "offset", -1 },//偏移
                    });
                return;
            }

            //触发事件
            up6_biz_event.file_post_block(fileSvr.id,fileSvr.blockIndex);

            //生成信息
            JObject o = new JObject();
            o["msg"] = "ok";
            o["offset"] = fileSvr.blockOffset;//偏移
            //回传给控件
            if(fileSvr.blockIndex==1) 
                o["fields"]= new JObject {{ "object_id", fileSvr.object_id}
            };
            var msg = JsonConvert.SerializeObject(o);
            this.toContentJson(msg);
        }
    }
}